﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NMF.Optimizations.Linq
{
    abstract class SelectingSource<TItem, TElement, TTarget> : ObservableSourceBase<TTarget>
    {
        public IObservableSource<TItem> Source { get; private set; }
        public Func<TItem, TElement, TTarget> Selector { get; private set; }

        private struct ItemElementTuple
        {
            public TItem Item { get; set; }
            public TElement Element { get; set; }

            public override int GetHashCode()
            {
                var i = 0;
                if (Item != null) i ^= Item.GetHashCode();
                if (Element != null) i ^= Element.GetHashCode();
                return i;
            }

            public override bool Equals(object obj)
            {
                if (!(obj is ItemElementTuple)) return false;
                var tuple = (ItemElementTuple)obj;
                return object.Equals(tuple.Element, Element) && object.Equals(tuple.Item, Item);
            }
        }

        private Dictionary<ItemElementTuple, TTarget> selectorDict = new Dictionary<ItemElementTuple, TTarget>();
        public TTarget Select(TItem item, TElement element)
        {
            var tuple = new ItemElementTuple()
            {
                Item = item,
                Element = element
            };

            TTarget target;
            if (!selectorDict.TryGetValue(tuple, out target))
            {
                target = Selector(item, element);
                selectorDict.Add(tuple, target);
            }
            return target;
        }

        public TTarget SelectAndRemove(TItem item, TElement element)
        {
            var tuple = new ItemElementTuple()
            {
                Item = item,
                Element = element
            };

            TTarget target;
            if (!selectorDict.TryGetValue(tuple, out target))
            {
                target = Selector(item, element);
            }
            else
            {
                selectorDict.Remove(tuple);
            }
            return target;
        }

        public void Swap(TItem item, TElement element, TTarget newTargetItem)
        {
            var tuple = new ItemElementTuple()
            {
                Item = item,
                Element = element
            };

            selectorDict[tuple] = newTargetItem;
        }

        public SelectingSource(IObservableSource<TItem> source, Func<TItem, TElement, TTarget> selector)
        {
            if (source == null) throw new ArgumentNullException("source");
            if (selector == null) throw new ArgumentNullException("selector");

            Source = source;
            Selector = selector;
        }

        protected override void OnReset()
        {
            selectorDict.Clear();
            base.OnReset();
        }
    }
}
